package rbacassessment_test

import (
	"testing"

	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/utils/ptr"

	"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
	"github.com/aquasecurity/trivy-operator/pkg/configauditreport"
	"github.com/aquasecurity/trivy-operator/pkg/rbacassessment"
	"github.com/aquasecurity/trivy-operator/pkg/trivyoperator"

	. "github.com/onsi/gomega"
)

func TestReportBuilder(t *testing.T) {

	t.Run("Should build report for namespaced resource", func(t *testing.T) {
		g := NewGomegaWithT(t)

		report, err := rbacassessment.NewReportBuilder(scheme.Scheme).
			Controller(&rbacv1.Role{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Role",
					APIVersion: "rbac.authorization.k8s.io/v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "some-owner",
					Namespace: "qa",
					Labels:    labels.Set{"tier": "tier-1", "owner": "team-a"},
				},
				Rules: []rbacv1.PolicyRule{},
			}).
			ResourceSpecHash("xyz").
			PluginConfigHash("nop").
			Data(v1alpha1.RbacAssessmentReportData{}).
			ResourceLabelsToInclude([]string{"tier"}).
			GetReport()
		g.Expect(err).ToNot(HaveOccurred())
		assessmentReport := rbacReport()
		g.Expect(report).To(Equal(assessmentReport))
	})

	t.Run("Should build report for namespaced resource with capital letter", func(t *testing.T) {
		g := NewGomegaWithT(t)

		report, err := rbacassessment.NewReportBuilder(scheme.Scheme).
			Controller(&rbacv1.Role{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Role",
					APIVersion: "rbac.authorization.k8s.io/v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "some-Owner",
					Namespace: "qa",
					Labels:    labels.Set{"tier": "tier-1", "owner": "team-a"},
				},
				Rules: []rbacv1.PolicyRule{},
			}).
			ResourceSpecHash("xyz").
			PluginConfigHash("nop").
			Data(v1alpha1.RbacAssessmentReportData{}).
			ResourceLabelsToInclude([]string{"tier"}).
			GetReport()
		g.Expect(err).ToNot(HaveOccurred())
		assessmentReport := v1alpha1.RbacAssessmentReport{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "role-5ccc5d4cff",
				Namespace: "qa",
				OwnerReferences: []metav1.OwnerReference{
					{
						APIVersion:         "rbac.authorization.k8s.io/v1",
						Kind:               "Role",
						Name:               "some-Owner",
						Controller:         ptr.To[bool](true),
						BlockOwnerDeletion: ptr.To[bool](false),
					},
				},
				Labels: map[string]string{
					trivyoperator.LabelResourceKind:      "Role",
					trivyoperator.LabelResourceName:      "some-Owner",
					trivyoperator.LabelResourceNamespace: "qa",
					trivyoperator.LabelResourceSpecHash:  "xyz",
					trivyoperator.LabelPluginConfigHash:  "nop",
					trivyoperator.LabelK8SAppManagedBy:   trivyoperator.AppTrivyOperator,
					"tier":                               "tier-1",
				},
			},
			Report: v1alpha1.RbacAssessmentReportData{},
		}
		g.Expect(report).To(Equal(assessmentReport))
	})

	t.Run("Should build report for cluster scoped resource", func(t *testing.T) {
		g := NewGomegaWithT(t)

		report, err := rbacassessment.NewReportBuilder(scheme.Scheme).
			Controller(&rbacv1.ClusterRole{
				TypeMeta: metav1.TypeMeta{
					Kind:       "ClusterRole",
					APIVersion: "rbac.authorization.k8s.io/v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:   "system:controller:node-controller",
					Labels: labels.Set{"tier": "tier-1", "owner": "team-a"},
				},
			}).
			ResourceSpecHash("xyz").
			PluginConfigHash("nop").
			Data(v1alpha1.RbacAssessmentReportData{}).
			ResourceLabelsToInclude([]string{"tier"}).
			GetClusterReport()

		g.Expect(err).ToNot(HaveOccurred())
		g.Expect(report).To(Equal(v1alpha1.ClusterRbacAssessmentReport{
			ObjectMeta: metav1.ObjectMeta{
				Name: "clusterrole-6f69bb5b79",
				OwnerReferences: []metav1.OwnerReference{
					{
						APIVersion:         "rbac.authorization.k8s.io/v1",
						Kind:               "ClusterRole",
						Name:               "system:controller:node-controller",
						Controller:         ptr.To(true),
						BlockOwnerDeletion: ptr.To(false),
					},
				},
				Labels: map[string]string{
					trivyoperator.LabelResourceKind:      "ClusterRole",
					trivyoperator.LabelResourceNameHash:  "6f69bb5b79",
					trivyoperator.LabelResourceNamespace: "",
					trivyoperator.LabelResourceSpecHash:  "xyz",
					trivyoperator.LabelPluginConfigHash:  "nop",
					trivyoperator.LabelK8SAppManagedBy:   trivyoperator.AppTrivyOperator,
					"tier":                               "tier-1",
				},
				Annotations: map[string]string{
					trivyoperator.LabelResourceName: "system:controller:node-controller",
				},
			},
			Report: v1alpha1.RbacAssessmentReportData{},
		}))
	})

	// Add test to verify config audit reports don't accidentally get RBAC assessment labels
	t.Run("Should build config audit report for cluster scoped resource with managed-by label", func(t *testing.T) {
		g := NewGomegaWithT(t)

		report, err := configauditreport.NewReportBuilder(scheme.Scheme).
			Controller(&rbacv1.ClusterRole{
				TypeMeta: metav1.TypeMeta{
					Kind:       "ClusterRole",
					APIVersion: "rbac.authorization.k8s.io/v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:   "system:controller:node-controller",
					Labels: labels.Set{"tier": "tier-1", "owner": "team-a"},
				},
			}).
			ResourceSpecHash("xyz").
			PluginConfigHash("nop").
			Data(v1alpha1.ConfigAuditReportData{}).
			ResourceLabelsToInclude([]string{"tier"}).
			GetClusterReport()

		g.Expect(err).ToNot(HaveOccurred())
		g.Expect(report).To(Equal(v1alpha1.ClusterConfigAuditReport{
			ObjectMeta: metav1.ObjectMeta{
				Name: "clusterrole-6f69bb5b79",
				OwnerReferences: []metav1.OwnerReference{
					{
						APIVersion:         "rbac.authorization.k8s.io/v1",
						Kind:               "ClusterRole",
						Name:               "system:controller:node-controller",
						Controller:         ptr.To[bool](true),
						BlockOwnerDeletion: ptr.To[bool](false),
					},
				},
				Labels: map[string]string{
					trivyoperator.LabelResourceKind:      "ClusterRole",
					trivyoperator.LabelResourceNameHash:  "6f69bb5b79",
					trivyoperator.LabelResourceNamespace: "",
					trivyoperator.LabelResourceSpecHash:  "xyz",
					trivyoperator.LabelPluginConfigHash:  "nop",
					trivyoperator.LabelK8SAppManagedBy:   trivyoperator.AppTrivyOperator,
					"tier":                               "tier-1",
				},
				Annotations: map[string]string{
					trivyoperator.LabelResourceName: "system:controller:node-controller",
				},
			},
			Report: v1alpha1.ConfigAuditReportData{},
		}))
	})

	t.Run("Should build RBAC assessment report with additional labels", func(t *testing.T) {
		g := NewGomegaWithT(t)

		additionalLabels := map[string]string{
			"security-scan": "enabled",
			"team":          "security",
		}

		report, err := rbacassessment.NewReportBuilder(scheme.Scheme).
			Controller(&rbacv1.Role{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Role",
					APIVersion: "rbac.authorization.k8s.io/v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-role",
					Namespace: "production",
					Labels:    labels.Set{"app": "web-server", "version": "v1.0"},
				},
				Rules: []rbacv1.PolicyRule{
					{
						APIGroups: []string{""},
						Resources: []string{"pods"},
						Verbs:     []string{"get", "list"},
					},
				},
			}).
			ResourceSpecHash("abc123").
			PluginConfigHash("def456").
			Data(v1alpha1.RbacAssessmentReportData{}).
			ResourceLabelsToInclude([]string{"app"}).
			AdditionalReportLabels(additionalLabels).
			GetReport()

		g.Expect(err).ToNot(HaveOccurred())

		// Verify managed-by label is always present
		g.Expect(report.Labels).To(HaveKeyWithValue(trivyoperator.LabelK8SAppManagedBy, trivyoperator.AppTrivyOperator))

		// Verify additional labels are included
		for key, value := range additionalLabels {
			g.Expect(report.Labels).To(HaveKeyWithValue(key, value))
		}

		// Verify included resource labels
		g.Expect(report.Labels).To(HaveKeyWithValue("app", "web-server"))

		// Verify core trivy-operator labels
		g.Expect(report.Labels).To(HaveKeyWithValue(trivyoperator.LabelResourceKind, "Role"))
		g.Expect(report.Labels).To(HaveKeyWithValue(trivyoperator.LabelResourceName, "test-role"))
		g.Expect(report.Labels).To(HaveKeyWithValue(trivyoperator.LabelResourceNamespace, "production"))
	})
}

func rbacReport() v1alpha1.RbacAssessmentReport {
	return v1alpha1.RbacAssessmentReport{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "role-some-owner",
			Namespace: "qa",
			OwnerReferences: []metav1.OwnerReference{
				{
					APIVersion:         "rbac.authorization.k8s.io/v1",
					Kind:               "Role",
					Name:               "some-owner",
					Controller:         ptr.To[bool](true),
					BlockOwnerDeletion: ptr.To[bool](false),
				},
			},
			Labels: map[string]string{
				trivyoperator.LabelResourceKind:      "Role",
				trivyoperator.LabelResourceName:      "some-owner",
				trivyoperator.LabelResourceNamespace: "qa",
				trivyoperator.LabelResourceSpecHash:  "xyz",
				trivyoperator.LabelPluginConfigHash:  "nop",
				trivyoperator.LabelK8SAppManagedBy:   trivyoperator.AppTrivyOperator,
				"tier":                               "tier-1",
			},
		},
		Report: v1alpha1.RbacAssessmentReportData{},
	}
}
